diff --git a/benchmarks/swtbench/eval_infer.py b/benchmarks/swtbench/eval_infer.py index 94cb120a..53396be7 100644 --- a/benchmarks/swtbench/eval_infer.py +++ b/benchmarks/swtbench/eval_infer.py @@ -10,11 +10,14 @@ """ import argparse +import contextlib import json import os import shutil import subprocess import sys +import time +from datetime import datetime from pathlib import Path from benchmarks.utils.laminar import LaminarService @@ -26,6 +29,54 @@ logger = get_logger(__name__) +def _now() -> str: + """Return an ISO8601 UTC timestamp for logging.""" + return datetime.utcnow().isoformat() + "Z" + + +@contextlib.contextmanager +def _chdir(path: Path): + """Temporarily change working directory.""" + original = Path.cwd() + os.chdir(path) + try: + yield + finally: + os.chdir(original) + + +class TimingRecorder: + """Lightweight timing helper to capture named durations.""" + + def __init__(self) -> None: + self._origin = time.perf_counter() + self._events: dict[str, float] = {} + + def mark(self, name: str) -> None: + self._events[name] = time.perf_counter() + + def elapsed(self, start: str, end: str | None = None) -> float: + if end is None: + end = start + start = "start" + start_time = self._events.get(start, self._origin) + end_time = self._events.get(end, time.perf_counter()) + return end_time - start_time + + def summary(self) -> dict[str, float]: + keys = sorted(self._events.keys(), key=self._events.get) + if "start" not in self._events: + keys.insert(0, "start") + self._events["start"] = self._origin + summary: dict[str, float] = {} + for idx, key in enumerate(keys): + next_key = keys[idx + 1] if idx + 1 < len(keys) else None + start_time = self._events[key] + end_time = self._events.get(next_key, time.perf_counter()) + summary[key] = end_time - start_time + return summary + + def _load_prediction_instance_ids(predictions_file: Path) -> list[str]: instance_ids: list[str] = [] seen = set() @@ -176,119 +227,241 @@ def run_swtbench_evaluation( predictions_file: str, dataset: str = "eth-sri/SWT-bench_Verified_bm25_27k_zsp", workers: str = "12", + model_name: str = "OpenHands", ) -> None: """ Run SWT-Bench evaluation on the predictions file. - Note: The swt-bench package is included as a dependency in pyproject.toml - to ensure all its dependencies are available, but the package itself is not - properly structured for import. We use subprocess to run it from a cached - clone since that's how the upstream package is designed to work. - Args: predictions_file: Path to the SWT-Bench format predictions file dataset: SWT-Bench dataset to evaluate against workers: Number of workers to use for evaluation + model_name: Model name stored in the predictions file/report """ - logger.info(f"Running SWT-Bench evaluation on {predictions_file}") + logger.info("Running SWT-Bench evaluation on %s", predictions_file) - try: - # Use a global cache directory for SWT-Bench source - cache_dir = Path.home() / ".cache" / "openhands" / "swt-bench" - swt_bench_dir = cache_dir / "swt-bench" - - # Clone SWT-Bench repository if it doesn't exist - if not swt_bench_dir.exists(): - logger.info("Setting up SWT-Bench source in global cache...") - cache_dir.mkdir(parents=True, exist_ok=True) - - logger.info("Cloning SWT-Bench repository...") - clone_cmd = [ - "git", - "clone", - "https://github.com/logic-star-ai/swt-bench.git", - str(swt_bench_dir), - ] - result = subprocess.run(clone_cmd, text=True) - if result.returncode != 0: - raise subprocess.CalledProcessError(result.returncode, clone_cmd) - - logger.info(f"SWT-Bench source installed at {swt_bench_dir}") - - # Get the directory and filename of the predictions file - predictions_path = Path(predictions_file).resolve() - predictions_filename = predictions_path.name - - # Copy predictions file to swt-bench directory - swt_predictions_file = swt_bench_dir / predictions_filename - shutil.copy2(predictions_file, swt_predictions_file) - - # Run SWT-Bench evaluation by running python directly from the swt-bench directory - # but using the uv environment's python executable which has all dependencies - benchmarks_dir = Path(__file__).parent.parent.parent - - # Get the python executable from the uv environment - python_executable = subprocess.run( - [ - "uv", - "run", - "--directory", - str(benchmarks_dir), - "python", - "-c", - "import sys; print(sys.executable)", - ], - capture_output=True, - text=True, - cwd=benchmarks_dir, - ).stdout.strip() - - # Set up environment with PYTHONPATH to include swt-bench directory - env = os.environ.copy() - env["PYTHONPATH"] = str(swt_bench_dir) - - cmd = [ - python_executable, - "src/main.py", # Run as script instead of module - "--dataset_name", - dataset, - "--predictions_path", - predictions_filename, - "--filter_swt", - "--max_workers", - str(workers), - "--run_id", - f"eval_{predictions_path.stem}", + timers = TimingRecorder() + timers.mark("start") + + cache_dir = Path( + os.getenv( + "SWT_BENCH_CACHE_DIR", + Path.home() / ".cache" / "openhands" / "swt-bench", + ) + ) + repo_override = os.getenv("SWT_BENCH_REPO_PATH") + repo_candidates = [ + Path(p) for p in ([repo_override] if repo_override else []) if p + ] + repo_candidates.append(Path("/opt/swt-bench")) + repo_candidates.append(cache_dir / "swt-bench") + + hf_cache = Path( + os.getenv( + "HF_HOME", + os.getenv("SWT_BENCH_HF_CACHE", cache_dir / "huggingface"), + ) + ) + hf_datasets_cache = Path( + os.getenv( + "HF_DATASETS_CACHE", + os.getenv("SWT_BENCH_HF_DATASETS_CACHE", hf_cache / "datasets"), + ) + ) + + cache_dir.mkdir(parents=True, exist_ok=True) + hf_cache.mkdir(parents=True, exist_ok=True) + hf_datasets_cache.mkdir(parents=True, exist_ok=True) + + swt_bench_dir = None + for candidate in repo_candidates: + if candidate and candidate.exists(): + swt_bench_dir = candidate + break + + if swt_bench_dir is None: + swt_bench_dir = repo_candidates[-1] + logger.info( + "SWT-Bench source not found; cloning into %s (started %s)", + swt_bench_dir, + _now(), + ) + swt_bench_dir.parent.mkdir(parents=True, exist_ok=True) + + clone_cmd = [ + "git", + "clone", + "https://github.com/logic-star-ai/swt-bench.git", + str(swt_bench_dir), ] + result = subprocess.run(clone_cmd, text=True) + if result.returncode != 0: + raise subprocess.CalledProcessError(result.returncode, clone_cmd) + + logger.info("SWT-Bench source installed at %s", swt_bench_dir) + else: + logger.info("Using existing SWT-Bench source at %s", swt_bench_dir) + + os.environ.setdefault("SWT_BENCH_CACHE_DIR", str(cache_dir)) + os.environ.setdefault("SWT_BENCH_REPO_PATH", str(swt_bench_dir)) + os.environ.setdefault("HF_HOME", str(hf_cache)) + os.environ.setdefault("HF_DATASETS_CACHE", str(hf_datasets_cache)) + + predictions_path = Path(predictions_file).resolve() + run_id = f"eval_{predictions_path.stem}" + max_workers = int(workers) if isinstance(workers, str) else workers + cache_level = os.getenv("SWT_BENCH_CACHE_LEVEL", "env") + clean_images = os.getenv("SWT_BENCH_CLEAN_IMAGES", "false").lower() in ( + "1", + "true", + "yes", + ) + force_rebuild = os.getenv("SWT_BENCH_FORCE_REBUILD", "false").lower() in ( + "1", + "true", + "yes", + ) + build_mode = os.getenv("SWT_BENCH_BUILD_MODE", "api") + + dataset_durations: list[float] = [] + run_timings: dict[str, float] = {} + run_timestamps: dict[str, str] = {} + report_file: Path | None = None - logger.info(f"Using Python executable: {python_executable}") - logger.info(f"Running command: {' '.join(cmd)}") - logger.info(f"Working directory: {swt_bench_dir}") - logger.info(f"PYTHONPATH: {env['PYTHONPATH']}") - logger.info("SWT-Bench evaluation output:") - print("-" * 80) - - # Stream output directly to console, running from swt-bench directory - result = subprocess.run(cmd, text=True, cwd=swt_bench_dir, env=env) - - print("-" * 80) - if result.returncode == 0: - logger.info("SWT-Bench evaluation completed successfully") - else: - logger.error( - f"SWT-Bench evaluation failed with return code {result.returncode}" + logger.info("HF_HOME=%s HF_DATASETS_CACHE=%s", hf_cache, hf_datasets_cache) + logger.info( + "SWT-Bench run_id=%s cache_level=%s clean_images=%s force_rebuild=%s", + run_id, + cache_level, + clean_images, + force_rebuild, + ) + + src_path = swt_bench_dir / "src" + + with _chdir(swt_bench_dir): + # Ensure swt-bench sources (including src/run_evaluation.py) are importable + if src_path.exists(): + sys.path.insert(0, str(src_path)) + sys.path.insert(0, str(swt_bench_dir)) + try: + import src.dataset as swt_dataset + import src.main as swt_main + import src.run_evaluation as swt_run_eval + except Exception: + logger.error("Failed to import swt-bench modules from %s", swt_bench_dir) + raise + + original_get_dataset = swt_dataset.get_dataset_from_preds + original_run_instances = swt_run_eval.run_instances + + def _timed_get_dataset_from_preds(*args, **kwargs): + start = time.perf_counter() + result = original_get_dataset(*args, **kwargs) + duration = time.perf_counter() - start + dataset_durations.append(duration) + logger.info( + "Dataset load/prep finished in %.2fs at %s (instances=%s)", + duration, + _now(), + len(result) if result else 0, ) - raise subprocess.CalledProcessError(result.returncode, cmd) + return result + + def _timed_run_instances(*args, **kwargs): + run_timings["start"] = time.perf_counter() + run_timestamps["start"] = _now() + try: + return original_run_instances(*args, **kwargs) + finally: + run_timings["end"] = time.perf_counter() + run_timestamps["end"] = _now() + logger.info( + "SWT-Bench run_instances completed in %.2fs (start=%s end=%s)", + run_timings["end"] - run_timings["start"], + run_timestamps.get("start", "unknown"), + run_timestamps.get("end", "unknown"), + ) - except FileNotFoundError: - logger.error( - "SWT-Bench evaluation command not found. " - "Make sure git and python are available." + swt_dataset.get_dataset_from_preds = _timed_get_dataset_from_preds + swt_run_eval.run_instances = _timed_run_instances + + timers.mark("swtbench_start") + logger.info( + "Starting SWT-Bench harness (run_id=%s) at %s with %s workers", + run_id, + _now(), + max_workers, ) - raise - except Exception as e: - logger.error(f"Error running SWT-Bench evaluation: {e}") - raise + + try: + swt_main.run( + dataset_name=dataset, + is_swt=False, + split="test", + instance_ids=None, + predictions_path=str(predictions_path), + compute_coverage=True, + max_workers=max_workers, + force_rebuild=force_rebuild, + cache_level=cache_level, + clean=clean_images, + open_file_limit=4096, + run_id=run_id, + patch_types=["vanilla"], + timeout=1800, + filter_swt=True, + build_mode=build_mode, + skip_eval=False, + exec_mode="unit_test", + reproduction_script_name=None, + ) + except Exception: + logger.exception("SWT-Bench evaluation failed") + raise + finally: + swt_dataset.get_dataset_from_preds = original_get_dataset + swt_run_eval.run_instances = original_run_instances + timers.mark("swtbench_end") + + report_dir = swt_bench_dir / "evaluation_results" + model_name_safe = model_name.replace("/", "__") + report_file = report_dir / f"{model_name_safe}.{run_id}.json" + logger.info("Expected report at %s", report_file) + + timers.mark("end") + + if report_file and report_file.exists(): + logger.info("SWT-Bench evaluation completed successfully at %s", _now()) + else: + logger.error("SWT-Bench evaluation finished without report output") + raise FileNotFoundError(f"Report file not found: {report_file}") + + if dataset_durations: + total_dataset_time = sum(dataset_durations) + logger.info( + "Dataset load/preprocessing time: %.2fs over %d call(s)", + total_dataset_time, + len(dataset_durations), + ) + else: + logger.info("Dataset load timing unavailable (no calls recorded)") + + if run_timings: + logger.info( + "SWT-Bench run_instances timing: start=%s end=%s duration=%.2fs", + run_timestamps.get("start", "unknown"), + run_timestamps.get("end", "unknown"), + run_timings["end"] - run_timings["start"], + ) + + total_duration = timers.elapsed("start", "end") + harness_duration = timers.elapsed("swtbench_start", "swtbench_end") + logger.info( + "SWT-Bench evaluation total duration: %.2fs (harness: %.2fs)", + total_duration, + harness_duration, + ) def main() -> None: @@ -365,11 +538,23 @@ def main() -> None: if not args.skip_evaluation: # Run evaluation - run_swtbench_evaluation(str(output_file), args.dataset, args.workers) + run_swtbench_evaluation( + str(output_file), + args.dataset, + args.workers, + args.model_name, + ) # Move SWT-Bench evaluation report to same folder as output.jsonl - cache_dir = Path.home() / ".cache" / "openhands" / "swt-bench" - swt_bench_dir = cache_dir / "swt-bench" + cache_dir = Path( + os.getenv( + "SWT_BENCH_CACHE_DIR", + Path.home() / ".cache" / "openhands" / "swt-bench", + ) + ) + swt_bench_dir = Path( + os.getenv("SWT_BENCH_REPO_PATH", cache_dir / "swt-bench") + ) report_dir = swt_bench_dir / "evaluation_results" run_id = f"eval_{output_file.stem}" model_name_safe = args.model_name.replace("/", "__") diff --git a/pyproject.toml b/pyproject.toml index 0ecd0736..b5f82b5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,8 @@ dependencies = [ "fastmcp>=2.11.3", "httpx>=0.27.0", "litellm>=1.77.7.dev9", - "pydantic>=2.12.0", + "pydantic>=2.12.5", + "pydantic-core>=2.41.5", "python-frontmatter>=1.1.0", "python-json-logger>=3.3.0", "tenacity>=9.1.2", diff --git a/uv.lock b/uv.lock index 9639461b..471b0a07 100644 --- a/uv.lock +++ b/uv.lock @@ -1712,7 +1712,7 @@ wheels = [ [[package]] name = "lmnr" -version = "0.7.25" +version = "0.7.27" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio", version = "1.67.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, @@ -1733,9 +1733,9 @@ dependencies = [ { name = "tenacity" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/bd/a65219ca6f09199e35a14a55acb503e3ac896db15018d342076bd24401e1/lmnr-0.7.25.tar.gz", hash = "sha256:a3a0ba9a305243bbe97f2fcb8afc7d39d201dc11107b4633c257b64b838b2979", size = 203876, upload-time = "2025-12-18T17:31:24.507Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/5f/a17be521738630f674831d865abaf16df3286d7d5f461f2a8c5f985a8d15/lmnr-0.7.27.tar.gz", hash = "sha256:2f541b1d8d110f4edeba814e6bdde0e14ef8a07df1be696d0fdee0ba759c8f6d", size = 204415, upload-time = "2026-01-12T20:02:30.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/35/1f22e3fea98416d58dddbdbc63e18ddcfb2b8f850b8ec065652a90d99666/lmnr-0.7.25-py3-none-any.whl", hash = "sha256:c0539d5f8c8e59a2d5d0ab04e498a82351d51fde8cf04ef8b312424e0be537ac", size = 266040, upload-time = "2025-12-18T17:31:22.986Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/0f9674fa37688265d8711d7607849e45d2e9b9421ce8c653d2f2de6733c0/lmnr-0.7.27-py3-none-any.whl", hash = "sha256:600f43265128c92003b72531f12d614568949688305a1841333912e2320f1026", size = 266573, upload-time = "2026-01-12T20:02:28.216Z" }, ] [[package]] @@ -2326,6 +2326,7 @@ dependencies = [ { name = "pandas" }, { name = "pillow" }, { name = "pydantic" }, + { name = "pydantic-core" }, { name = "pyright", extra = ["nodejs"] }, { name = "pytest-json-report" }, { name = "python-dotenv" }, @@ -2376,7 +2377,8 @@ requires-dist = [ { name = "openhands-workspace", editable = "vendor/software-agent-sdk/openhands-workspace" }, { name = "pandas" }, { name = "pillow" }, - { name = "pydantic", specifier = ">=2.12.0" }, + { name = "pydantic", specifier = ">=2.12.5" }, + { name = "pydantic-core", specifier = ">=2.41.5" }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.405" }, { name = "pytest-json-report" }, { name = "python-dotenv" }, @@ -3138,21 +3140,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, @@ -3161,16 +3149,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },