diff --git a/openverifiablellm/manifest_chain.py b/openverifiablellm/manifest_chain.py new file mode 100644 index 0000000..fe9bcc1 --- /dev/null +++ b/openverifiablellm/manifest_chain.py @@ -0,0 +1,288 @@ +""" +manifest_chain.py +================= +Cryptographically linked manifest chains for tamper detection. + +This module provides utilities to create and verify a chain of dataset manifests, +where each manifest includes the SHA256 hash of its predecessor. This forms a +tamper-evident chain: if any manifest in the sequence is modified, the hash +stored in the next manifest no longer matches, and the tampering is immediately +visible (analogous to a wax seal chain). + +Usage +----- +# Generate parent hash before writing a new manifest + parent_hash = get_parent_manifest_hash(manifest_path) + manifest = { ... , "parent_manifest_hash": parent_hash } + +# Verify the chain between two consecutive manifests + is_valid = verify_manifest_chain_link(previous_manifest_path, current_manifest) + +# Verify entire chain from root + report = verify_manifest_chain(current_manifest_path) +""" + +import hashlib +import json +import logging +from pathlib import Path +from typing import Any, Dict, Optional, Union + +logger = logging.getLogger(__name__) + + +def _canonical_json(obj: Any) -> str: + """ + Serialize object into canonical JSON format. + Ensures stable hashing across runs regardless of key order. + + Parameters + ---------- + obj : Any + JSON-serializable object + + Returns + ------- + str + Canonical JSON string with sorted keys + """ + return json.dumps(obj, sort_keys=True, separators=(",", ":")) + + +def compute_manifest_hash(manifest: Union[str, Path, Dict[str, Any]]) -> str: + """ + Compute SHA-256 hash of a manifest. + + Can accept: + - Dict: manifest data object (will be canonical-JSON serialized) + - str/Path: path to manifest JSON file (will be read and parsed) + + Parameters + ---------- + manifest : Union[str, Path, Dict[str, Any]] + Either a manifest dict or a path to a manifest JSON file + + Returns + ------- + str + SHA-256 hash in hexadecimal format + + Raises + ------ + FileNotFoundError + If manifest is a path and the file does not exist + ValueError + If manifest JSON is malformed + """ + if isinstance(manifest, dict): + manifest_data = manifest + else: + manifest_path = Path(manifest) + if not manifest_path.exists(): + raise FileNotFoundError(f"Manifest not found: {manifest_path}") + + with manifest_path.open("r", encoding="utf-8") as f: + try: + manifest_data = json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f"Malformed manifest JSON: {e}") + + hashable = manifest_data.copy() + if isinstance(hashable, dict): + hashable.pop("parent_manifest_hash", None) + + canonical = _canonical_json(hashable) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + +def get_parent_manifest_hash( + manifest_path: Union[str, Path], +) -> str: + """ + Read the SHA256 hash of the manifest at manifest_path, to be stored + as parent_manifest_hash in the next (replacement) manifest. + + If the file does not exist yet (first run), returns an empty string. + + Parameters + ---------- + manifest_path : Union[str, Path] + Path to the manifest file + + Returns + ------- + str + SHA-256 hash of the existing manifest, or "" if it doesn't exist + """ + path = Path(manifest_path) + + if not path.exists(): + logger.info("No previous manifest found at %s — parent_manifest_hash will be empty", path) + return "" + + try: + parent_hash = compute_manifest_hash(path) + logger.info("Parent manifest hash computed: %s", parent_hash) + return parent_hash + except Exception as e: + logger.exception("Could not compute parent manifest hash: %s", e) + raise + + +def verify_manifest_chain_link( + previous_manifest: Union[str, Path, Dict[str, Any]], + current_manifest: Union[str, Path, Dict[str, Any]], +) -> bool: + """ + Verify that current_manifest correctly references previous_manifest + via its parent_manifest_hash field. + + This checks a single link in the chain. If the link is broken, it indicates + that either: + - The previous manifest was tampered with/regenerated + - The current manifest's parent_manifest_hash was modified + + Parameters + ---------- + previous_manifest : Union[str, Path, Dict[str, Any]] + The previous manifest (dict or path to file) + current_manifest : Union[str, Path, Dict[str, Any]] + The current manifest (dict or path to file) + + Returns + ------- + bool + True if parent_manifest_hash in current matches the hash of previous + + Raises + ------ + FileNotFoundError + If any required file does not exist + ValueError + If manifest JSON is malformed + """ + # Load current manifest if needed + if isinstance(current_manifest, dict): + current_data = current_manifest + else: + current_path = Path(current_manifest) + if not current_path.exists(): + raise FileNotFoundError(f"Current manifest not found: {current_path}") + with current_path.open("r", encoding="utf-8") as f: + try: + current_data = json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f"Malformed current manifest JSON: {e}") + + # Get stored parent hash + stored_parent_hash = current_data.get("parent_manifest_hash", "") + + # Compute hash of previous manifest + expected_parent_hash = compute_manifest_hash(previous_manifest) + + # Compare + match = stored_parent_hash == expected_parent_hash + + if match: + logger.info("Manifest chain link verified ✓") + else: + logger.error( + "Manifest chain link broken! ✗\n" + " stored (in current) : %s\n" + " computed (from prev) : %s", + stored_parent_hash, + expected_parent_hash, + ) + + return match + + +def verify_manifest_chain( + manifest_path: Union[str, Path], + previous_manifest_path: Optional[Union[str, Path]] = None, +) -> Dict[str, Any]: + """ + Verify the manifest chain up to the given manifest. + + If previous_manifest_path is provided, checks the link between previous + and current. If not provided, checks that: + - parent_manifest_hash exists (for current run > 1) + - parent_manifest_hash is non-empty (indicates there was a previous run) + + Parameters + ---------- + manifest_path : Union[str, Path] + Path to the manifest to verify + previous_manifest_path : Optional[Union[str, Path]] + Path to the previous manifest (optional, for explicit link verification) + + Returns + ------- + Dict[str, Any] + Report with keys: + - "chain_valid": bool - whether the chain is intact + - "chain_message": str - human-readable message + - "has_parent_hash_field": bool - whether parent_manifest_hash field exists + - "parent_hash_value": str - value of parent_manifest_hash (or "") + """ + manifest_path = Path(manifest_path) + + if not manifest_path.exists(): + return { + "chain_valid": False, + "chain_message": f"Manifest not found: {manifest_path}", + "has_parent_hash_field": False, + "parent_hash_value": "", + } + + try: + with manifest_path.open("r", encoding="utf-8") as f: + manifest_data = json.load(f) + except Exception as e: + return { + "chain_valid": False, + "chain_message": f"Failed to read manifest: {e}", + "has_parent_hash_field": False, + "parent_hash_value": "", + } + + has_field = "parent_manifest_hash" in manifest_data + parent_hash_value = manifest_data.get("parent_manifest_hash", "") + + # If explicit previous manifest is provided, verify the link + if previous_manifest_path is not None: + try: + link_valid = verify_manifest_chain_link(previous_manifest_path, manifest_data) + message = ( + "Chain link verified against previous manifest ✓" + if link_valid + else "Chain link broken — previous manifest does not match stored hash ✗" + ) + except (OSError, ValueError) as exc: + link_valid = False + message = f"Failed to verify previous manifest: {exc}" + return { + "chain_valid": link_valid, + "chain_message": message, + "has_parent_hash_field": has_field, + "parent_hash_value": parent_hash_value, + } + + # Otherwise, just check that the field exists (indicating awareness of chain concept) + message = "" + if not has_field: + message = "parent_manifest_hash field missing (may be old manifest)" + chain_valid = False + elif parent_hash_value == "": + message = "parent_manifest_hash is empty (first run in chain)" + chain_valid = True + else: + message = "Cannot verify non-root manifest without previous_manifest_path" + chain_valid = False + + return { + "chain_valid": chain_valid, + "chain_message": message, + "has_parent_hash_field": has_field, + "parent_hash_value": parent_hash_value, + } diff --git a/openverifiablellm/tokenizer/__init__.py b/openverifiablellm/tokenizer/__init__.py index 10ea2ae..d4fb5d1 100644 --- a/openverifiablellm/tokenizer/__init__.py +++ b/openverifiablellm/tokenizer/__init__.py @@ -1,6 +1,8 @@ +from .factory import create_tokenizer from .train import hash_tokenizer_config, train_tokenizer __all__ = [ "train_tokenizer", "hash_tokenizer_config", + "create_tokenizer", ] diff --git a/openverifiablellm/tokenizer/bpe_tokenizer.py b/openverifiablellm/tokenizer/bpe_tokenizer.py index ac9fd7b..9a18d91 100644 --- a/openverifiablellm/tokenizer/bpe_tokenizer.py +++ b/openverifiablellm/tokenizer/bpe_tokenizer.py @@ -9,7 +9,6 @@ class BPETokenizer(BaseTokenizer): def train(self, text_file: Path, save_path: Path): - tokenizer = ByteLevelBPETokenizer() tokenizer.train( diff --git a/openverifiablellm/tokenizer/factory.py b/openverifiablellm/tokenizer/factory.py index d0b85ec..de004f6 100644 --- a/openverifiablellm/tokenizer/factory.py +++ b/openverifiablellm/tokenizer/factory.py @@ -3,7 +3,6 @@ def create_tokenizer(tokenizer_type, vocab_size, min_frequency): - tokenizer_type = tokenizer_type.lower() if tokenizer_type == "bpe": diff --git a/openverifiablellm/tokenizer/sentencepiece_tokenizer.py b/openverifiablellm/tokenizer/sentencepiece_tokenizer.py index 052112a..2a9c8b4 100644 --- a/openverifiablellm/tokenizer/sentencepiece_tokenizer.py +++ b/openverifiablellm/tokenizer/sentencepiece_tokenizer.py @@ -1,6 +1,11 @@ +import warnings from pathlib import Path -import sentencepiece as spm +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + # SWIG-generated modules (like sentencepiece on python 3.11+) emit deprecation warnings + # scoping the suppression here prevents it from spamming our test output + import sentencepiece as spm from .base import BaseTokenizer @@ -11,7 +16,6 @@ class SentencePieceTokenizer(BaseTokenizer): """ def train(self, text_file: Path, save_path: Path): - model_prefix = save_path / "spm" spm.SentencePieceTrainer.train( diff --git a/openverifiablellm/utils.py b/openverifiablellm/utils.py index ad9b7de..47cace0 100644 --- a/openverifiablellm/utils.py +++ b/openverifiablellm/utils.py @@ -15,6 +15,7 @@ import defusedxml.ElementTree as ET from openverifiablellm.environment import generate_environment_fingerprint +from openverifiablellm.manifest_chain import get_parent_manifest_hash logger = logging.getLogger(__name__) MERKLE_CHUNK_SIZE_BYTES = 1024 * 1024 # 1MB @@ -186,6 +187,72 @@ def verify_merkle_proof(chunk_bytes: bytes, proof, merkle_root: str) -> bool: # extract clean wikipage from actual wikipage +CHECKPOINT_INTERVAL = 1_000 # Save checkpoint every N pages + + +def _checkpoint_path(output_dir: Path) -> Path: + return output_dir / "wiki_clean.checkpoint.json" + + +def _compute_input_identity(input_path: Path) -> str: + """Return a stable identity for the input file.""" + return compute_sha256(file_path=input_path) + + +def _load_checkpoint(checkpoint_path: Path, input_path: Path, output_path: Path) -> Dict[str, Any]: + """Load checkpoint safely and validate resume conditions.""" + if not checkpoint_path.exists(): + return {"pages_processed": 0} + + try: + with checkpoint_path.open("r", encoding="utf-8") as f: + data = json.load(f) + + pages_processed = data.get("pages_processed") + stored_identity = data.get("input_identity") + + current_identity = _compute_input_identity(input_path) + + if not isinstance(pages_processed, int) or pages_processed < 0: + raise ValueError("Invalid pages_processed value") + + if stored_identity != current_identity: + raise ValueError("Input file changed since checkpoint") + + if pages_processed > 0 and not output_path.exists(): + raise ValueError("Output file missing; cannot safely resume") + + logger.info("Resuming from checkpoint: %d pages already processed", pages_processed) + + return data + + except Exception as e: + logger.warning("Checkpoint invalid (%s) — starting fresh.", e) + return {"pages_processed": 0} + + +def _save_checkpoint(checkpoint_path: Path, pages_processed: int, input_identity: str) -> None: + """Atomically save checkpoint with input identity.""" + tmp = checkpoint_path.with_suffix(".tmp") + + try: + checkpoint_data = { + "pages_processed": pages_processed, + "input_identity": input_identity, + } + + with tmp.open("w", encoding="utf-8") as f: + json.dump(checkpoint_data, f) + + tmp.replace(checkpoint_path) + + logger.debug("Checkpoint saved at %d pages", pages_processed) + + except Exception as e: + logger.warning("Failed to save checkpoint: %s", e) + tmp.unlink(missing_ok=True) + + def extract_text_from_xml(input_path, *, write_manifest: bool = False): """ Process a Wikipedia XML dump (compressed or uncompressed) into cleaned plain text. @@ -197,6 +264,12 @@ def extract_text_from_xml(input_path, *, write_manifest: bool = False): The processed output is saved to: data/processed/wiki_clean.txt + Supports resuming interrupted runs via a checkpoint file + (data/processed/wiki_clean.checkpoint.json). If the checkpoint + exists, already-processed pages are skipped and new pages are + appended to the existing output. Delete the checkpoint file to + force a full reprocessing from scratch. + Parameters ---------- input_path : str or Path @@ -215,6 +288,14 @@ def extract_text_from_xml(input_path, *, write_manifest: bool = False): output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / "wiki_clean.txt" + checkpoint_path = _checkpoint_path(output_dir) + + # Load checkpoint — tells us how many pages were already written + checkpoint = _load_checkpoint(checkpoint_path, input_path, output_path) + pages_already_done = checkpoint["pages_processed"] + + # If resuming, append to existing output; otherwise start fresh + write_mode = "a" if pages_already_done > 0 else "w" # Auto-detect file type using magic bytes separation with open(input_path, "rb") as test_f: @@ -222,23 +303,57 @@ def extract_text_from_xml(input_path, *, write_manifest: bool = False): open_func = bz2.open if is_bz2 else open - with open_func(input_path, "rb") as f: - context = ET.iterparse(f, events=("end",)) - - with open(output_path, "w", encoding="utf-8") as out: - for _, elem in context: - if elem.tag.endswith("page"): - text_elem = elem.find(".//{*}text") + pages_seen = 0 + pages_written = pages_already_done - if text_elem is not None and text_elem.text: - cleaned = clean_wikitext(text_elem.text) - if cleaned: - out.write(cleaned + "\n\n") + try: + with open_func(input_path, "rb") as f: + context = ET.iterparse(f, events=("end",)) + + with open(output_path, write_mode, encoding="utf-8") as out: + for _, elem in context: + if elem.tag.endswith("page"): + pages_seen += 1 + + # Skip pages already processed in a previous run + if pages_seen <= pages_already_done: + elem.clear() + continue + + text_elem = elem.find(".//{*}text") + + if text_elem is not None and text_elem.text: + cleaned = clean_wikitext(text_elem.text) + if cleaned: + out.write(cleaned + "\n\n") + + pages_written += 1 + elem.clear() + + # Flush output and save checkpoint periodically + if pages_written % CHECKPOINT_INTERVAL == 0: + out.flush() + _save_checkpoint(checkpoint_path, pages_written, input_path) + except KeyboardInterrupt: + _save_checkpoint(checkpoint_path, pages_written, input_path) + logger.warning("Interrupted by user after %d pages. Run again to resume.", pages_written) + raise + except Exception: + # Save progress before propagating the exception so the next run can resume + _save_checkpoint(checkpoint_path, pages_written, input_path) + logger.error("Processing interrupted after %d pages. Run again to resume.", pages_written) + raise - elem.clear() - logger.info("Preprocessing complete. Output saved to %s", output_path) + # Processing finished successfully — remove checkpoint so a fresh + # re-run (if ever needed) starts from the beginning if write_manifest: generate_manifest(input_path, output_path) + checkpoint_path.unlink(missing_ok=True) + logger.info( + "Preprocessing complete. %d pages processed. Output saved to %s", + pages_written, + output_path, + ) # generate data manifest @@ -251,6 +366,13 @@ def generate_manifest(raw_path, processed_path): f"Processed file not found at {processed_path}. Run preprocessing first." ) + project_root = Path.cwd() + manifest_path = project_root / "data" / "dataset_manifest.json" + + # ===== NEW: Compute parent_manifest_hash before creating new manifest ===== + parent_manifest_hash = get_parent_manifest_hash(manifest_path) + # ======================================================================== + manifest = { "wikipedia_dump": raw_path.name, "dump_date": extract_dump_date(raw_path.name), @@ -263,21 +385,26 @@ def generate_manifest(raw_path, processed_path): ), "chunk_size_bytes": MERKLE_CHUNK_SIZE_BYTES, # --------------------------------------------------------------- + # Add parent_manifest_hash to link to previous manifest + "parent_manifest_hash": parent_manifest_hash, "preprocessing_version": "v1", "python_version": platform.python_version(), } + env_data = generate_environment_fingerprint() manifest.update( {"environment": env_data["environment"], "environment_hash": env_data["environment_hash"]} ) - project_root = Path.cwd() - manifest_path = project_root / "data" / "dataset_manifest.json" manifest_path.parent.mkdir(parents=True, exist_ok=True) with open(manifest_path, "w") as f: json.dump(manifest, f, indent=2) logger.info("Manifest written to %s", manifest_path) + logger.info( + "Manifest parent hash: %s", + parent_manifest_hash if parent_manifest_hash else "(first run)", + ) def export_merkle_proof( @@ -358,22 +485,7 @@ def compute_sha256( Exactly one of `data` or `file_path` must be provided. """ - - if (data is None) == (file_path is None): - raise ValueError("Exactly one of 'data' or 'file_path' must be provided.") - - sha256 = hashlib.sha256() - - if data is not None: - sha256.update(data) - return sha256.hexdigest() - - path = Path(file_path) - with path.open("rb") as f: - while chunk := f.read(8192): - sha256.update(chunk) - - return sha256.hexdigest() + return compute_sha256_bytes(data=data, file_path=file_path).hex() def extract_dump_date(filename: str): diff --git a/openverifiablellm/verify.py b/openverifiablellm/verify.py index ad7f789..4edfbca 100644 --- a/openverifiablellm/verify.py +++ b/openverifiablellm/verify.py @@ -6,8 +6,10 @@ generated artifacts (SHA256, Merkle roots, manifest fields, environment hash) match the previously recorded manifest exactly. +Now includes cryptographic chain verification of manifests. + Usage (CLI): - python -m openverifiablellm.verify [--manifest ] + python scripts/verify_dataset.py [--manifest ] [--previous-manifest ] Usage (Python): from openverifiablellm.verify import verify_preprocessing @@ -30,6 +32,10 @@ from openverifiablellm import utils from openverifiablellm.environment import generate_environment_fingerprint +from openverifiablellm.manifest_chain import ( + verify_manifest_chain, + verify_manifest_chain_link, +) logger = logging.getLogger(__name__) @@ -67,6 +73,7 @@ class VerificationReport: input_dump: str manifest_path: str + previous_manifest_path: Optional[str] = None checks: list[CheckResult] = field(default_factory=list) def add(self, check: CheckResult) -> None: @@ -113,6 +120,8 @@ def row(col1, col2="", col3="", col4=""): # Metadata section lines.append(f"│ Input Dump : {self.input_dump:<88}│") lines.append(f"│ Manifest : {self.manifest_path:<88}│") + if self.previous_manifest_path: + lines.append(f"│ Previous : {self.previous_manifest_path:<88}│") lines.append("├" + line("─") + "┤") # Summary counts @@ -143,6 +152,7 @@ def to_dict(self) -> dict: return { "input_dump": self.input_dump, "manifest_path": self.manifest_path, + "previous_manifest_path": self.previous_manifest_path, "all_passed": self.all_passed, "counts": { "total": len(self.checks), @@ -195,6 +205,7 @@ def _load_manifest(manifest_path: Path) -> dict: def verify_preprocessing( input_dump: Union[str, Path], manifest_path: Optional[Union[str, Path]] = None, + previous_manifest_path: Optional[Union[str, Path]] = None, *, project_root: Optional[Path] = None, ) -> VerificationReport: @@ -209,6 +220,9 @@ def verify_preprocessing( manifest_path : Explicit path to ``dataset_manifest.json``. Defaults to ``/data/dataset_manifest.json``. + previous_manifest_path : + Optional path to the previous manifest for chain verification. + If provided, verifies that current manifest correctly references it. project_root : Root used to locate the default manifest. Defaults to ``Path.cwd()``. @@ -226,9 +240,13 @@ def verify_preprocessing( else: manifest_path = Path(manifest_path) + if previous_manifest_path is not None: + previous_manifest_path = Path(previous_manifest_path).resolve() + report = VerificationReport( input_dump=str(input_dump), manifest_path=str(manifest_path), + previous_manifest_path=str(previous_manifest_path) if previous_manifest_path else None, ) # 1. Load existing manifest @@ -261,6 +279,59 @@ def verify_preprocessing( ) ) + # ===== Verify manifest chain if previous manifest is provided ===== + if previous_manifest_path is not None: + if not previous_manifest_path.exists(): + report.add( + CheckResult( + name="manifest_chain_link", + status=CheckStatus.FAIL, + detail=f"Previous manifest not found: {previous_manifest_path}", + ) + ) + else: + try: + chain_valid = verify_manifest_chain_link(previous_manifest_path, manifest) + status = CheckStatus.PASS if chain_valid else CheckStatus.FAIL + report.add( + CheckResult( + name="manifest_chain_link", + status=status, + expected=previous_manifest_path.name, + actual="✓ linked" if chain_valid else "✗ broken", + detail="Verifies parent_manifest_hash matches previous manifest hash", + ) + ) + except (OSError, ValueError) as exc: + report.add( + CheckResult( + name="manifest_chain_link", + status=CheckStatus.FAIL, + detail=f"Chain verification error: {exc}", + ) + ) + else: + # Check if manifest has chain awareness (parent_manifest_hash field) + chain_report = verify_manifest_chain(manifest_path) + if "parent_manifest_hash" in manifest: + status = CheckStatus.PASS if chain_report["chain_valid"] else CheckStatus.SKIP + report.add( + CheckResult( + name="manifest_chain_awareness", + status=status, + detail=chain_report["chain_message"], + ) + ) + else: + report.add( + CheckResult( + name="manifest_chain_awareness", + status=CheckStatus.SKIP, + detail="Manifest predates chain feature (no parent_manifest_hash field)", + ) + ) + # ======================================================================== + # 2. Validate raw file integrity BEFORE re-processing if not input_dump.exists(): report.add( @@ -386,15 +457,17 @@ def verify_preprocessing( "environment_hash", expected=manifest.get("environment_hash"), actual=current_env["environment_hash"], - detail="Environment fingerprint comparison" + detail="Environment fingerprint comparison", ) else: - report.add(CheckResult( - name="environment_hash", - status=CheckStatus.SKIP, - detail="Field absent from manifest (older version)" - )) - + report.add( + CheckResult( + name="environment_hash", + status=CheckStatus.SKIP, + detail="Field absent from manifest (older version)", + ) + ) + # 4. Re-run preprocessing in an isolated temp directory tmp_dir = Path(tempfile.mkdtemp(prefix="ovllm_verify_")) try: @@ -409,11 +482,12 @@ def verify_preprocessing( pythonpath_entries.append(existing_pythonpath) env["PYTHONPATH"] = os.pathsep.join(dict.fromkeys(pythonpath_entries)) + script_path = Path(repo_root) / "scripts" / "preprocess_dump.py" + subprocess.run( [ sys.executable, - "-m", - "openverifiablellm.utils", + str(script_path), str(input_dump), ], cwd=tmp_dir, @@ -519,6 +593,7 @@ def verify_preprocessing( actual=reproduced_manifest.get("preprocessing_version"), detail="Preprocessing version tag", ) + if "chunk_size_bytes" in manifest: _check_field( report, @@ -527,6 +602,14 @@ def verify_preprocessing( actual=reproduced_manifest.get("chunk_size_bytes"), detail="Merkle chunk size used during preprocessing", ) + else: + report.add( + CheckResult( + name="manifest_chunk_size_bytes", + status=CheckStatus.SKIP, + detail="Field absent from manifest (older version)", + ) + ) else: report.add( CheckResult( @@ -561,6 +644,11 @@ def main(argv=None): default=None, help="Path to dataset_manifest.json (default: data/dataset_manifest.json)", ) + parser.add_argument( + "--previous-manifest", + default=None, + help="Path to previous manifest for chain verification", + ) parser.add_argument( "--json", dest="output_json", @@ -572,6 +660,7 @@ def main(argv=None): report = verify_preprocessing( input_dump=args.input_dump, manifest_path=args.manifest, + previous_manifest_path=args.previous_manifest, ) print(report.summary()) diff --git a/pyproject.toml b/pyproject.toml index 96523a0..9df0f79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ include = ["openverifiablellm*"] dev = [ "pytest>=7.0", "ruff>=0.15.4", + "numpy", ] [tool.pytest.ini_options] @@ -40,3 +41,5 @@ target-version = "py39" [tool.ruff.lint] select = ["E", "F", "I"] ignore = ["E501"] + +# Removing empty pytest options to avoid duplicate key conflict with main diff --git a/scripts/download_dump.py b/scripts/download_dump.py index e019e0b..4ecb339 100644 --- a/scripts/download_dump.py +++ b/scripts/download_dump.py @@ -267,7 +267,7 @@ def main(argv=None) -> None: ) print(f"\nDump ready at: {dest}") print("\nNext step — run preprocessing from the repository root:") - print(f' python -m openverifiablellm.utils "{dest}"') + print(f' python scripts/preprocess_dump.py "{dest}"') except RuntimeError as exc: logger.error("%s", exc) diff --git a/scripts/preprocess_dump.py b/scripts/preprocess_dump.py new file mode 100644 index 0000000..33c524b --- /dev/null +++ b/scripts/preprocess_dump.py @@ -0,0 +1,35 @@ +import argparse +import logging +from pathlib import Path + +from openverifiablellm.utils import extract_text_from_xml + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") + + +def main(): + parser = argparse.ArgumentParser( + description="Preprocess Wikipedia dump and generate dataset manifest." + ) + + parser.add_argument( + "input_dump", + help="Path to the Wikipedia XML dump (.xml or .bz2)", + ) + + parser.add_argument( + "--no-manifest", + action="store_true", + help="Skip manifest generation", + ) + + args = parser.parse_args() + + extract_text_from_xml( + Path(args.input_dump), + write_manifest=not args.no_manifest, + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/verify_dataset.py b/scripts/verify_dataset.py new file mode 100644 index 0000000..9ce5f36 --- /dev/null +++ b/scripts/verify_dataset.py @@ -0,0 +1,64 @@ +import argparse +import json +import logging +import sys +from pathlib import Path + +from openverifiablellm.verify import verify_preprocessing + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") + + +def main(): + parser = argparse.ArgumentParser( + description="Verify deterministic preprocessing and manifest integrity." + ) + + parser.add_argument( + "input_dump", + help="Path to raw Wikipedia dump", + ) + + parser.add_argument( + "--manifest", + default=None, + help="Path to dataset_manifest.json", + ) + + parser.add_argument( + "--previous-manifest", + default=None, + help="Previous manifest for chain verification", + ) + + parser.add_argument( + "--json", + dest="output_json", + default=None, + help="Optional JSON report output", + ) + + args = parser.parse_args() + + report = verify_preprocessing( + input_dump=args.input_dump, + manifest_path=args.manifest, + previous_manifest_path=args.previous_manifest, + ) + + print(report.summary()) + + if args.output_json: + out = Path(args.output_json) + out.parent.mkdir(parents=True, exist_ok=True) + + with out.open("w") as f: + json.dump(report.to_dict(), f, indent=2) + + print(f"\nJSON report written to: {out}") + + sys.exit(0 if report.all_passed else 1) + + +if __name__ == "__main__": + main() diff --git a/tests/test_manifest_chain.py b/tests/test_manifest_chain.py new file mode 100644 index 0000000..e28c6dd --- /dev/null +++ b/tests/test_manifest_chain.py @@ -0,0 +1,427 @@ +""" +Tests for Manifest Chain (tamper detection) feature. + +Tests the cryptographic linking of dataset manifests where each manifest +includes the SHA256 hash of its predecessor, enabling tamper detection. + +Run with: + python -m pytest tests/test_manifest_chain.py -v +""" + +import json + +import pytest + +from openverifiablellm.manifest_chain import ( + _canonical_json, + compute_manifest_hash, + get_parent_manifest_hash, + verify_manifest_chain, + verify_manifest_chain_link, +) + + +class TestCanonicalJson: + """Tests for canonical JSON serialization.""" + + def test_canonical_json_consistent_ordering(self): + """Same object serialized twice should be identical.""" + obj = {"z": 1, "a": 2, "m": 3} + + json1 = _canonical_json(obj) + json2 = _canonical_json(obj) + + assert json1 == json2 + + def test_canonical_json_key_order_normalized(self): + """Different key orders should produce the same output.""" + obj1 = {"z": 1, "a": 2} + obj2 = {"a": 2, "z": 1} + + assert _canonical_json(obj1) == _canonical_json(obj2) + + def test_canonical_json_no_spaces(self): + """Canonical JSON should have no extra spaces.""" + obj = {"key": "value"} + result = _canonical_json(obj) + + assert " " not in result + assert result == '{"key":"value"}' + + def test_canonical_json_nested_objects(self): + """Nested objects should also be canonically ordered.""" + obj = {"outer": {"z": 1, "a": 2}} + result = _canonical_json(obj) + + # The inner object keys should be sorted + assert '"a":2' in result + assert '"z":1' in result + # And a should come before z + assert result.index('"a":') < result.index('"z":') + + +class TestComputeManifestHash: + """Tests for compute_manifest_hash function.""" + + def test_hash_from_dict(self): + """Should compute hash from manifest dict.""" + manifest = {"key": "value", "number": 42} + hash_val = compute_manifest_hash(manifest) + + assert isinstance(hash_val, str) + assert len(hash_val) == 64 # SHA256 hex string length + + def test_hash_from_file(self, tmp_path): + """Should compute hash from manifest JSON file.""" + manifest = {"key": "value"} + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + hash_val = compute_manifest_hash(manifest_file) + + assert isinstance(hash_val, str) + assert len(hash_val) == 64 + + def test_hash_deterministic_dict(self): + """Same dict should always hash to same value.""" + manifest = {"a": 1, "b": 2} + + hash1 = compute_manifest_hash(manifest) + hash2 = compute_manifest_hash(manifest) + + assert hash1 == hash2 + + def test_hash_deterministic_different_key_order(self): + """Different key order should produce same hash.""" + manifest1 = {"z": 1, "a": 2} + manifest2 = {"a": 2, "z": 1} + + assert compute_manifest_hash(manifest1) == compute_manifest_hash(manifest2) + + def test_hash_excludes_parent_manifest_hash(self): + """Computing hash should ignore parent_manifest_hash field.""" + manifest1 = {"key": "value"} + manifest2 = {"key": "value", "parent_manifest_hash": "abc123"} + + # Both should hash the same since parent_manifest_hash is excluded + assert compute_manifest_hash(manifest1) == compute_manifest_hash(manifest2) + + def test_hash_file_not_found(self, tmp_path): + """Should raise FileNotFoundError if file doesn't exist.""" + with pytest.raises(FileNotFoundError): + compute_manifest_hash(tmp_path / "nonexistent.json") + + def test_hash_malformed_json(self, tmp_path): + """Should raise ValueError if JSON is malformed.""" + bad_file = tmp_path / "bad.json" + bad_file.write_text("{invalid json") + + with pytest.raises(ValueError): + compute_manifest_hash(bad_file) + + def test_hash_changes_on_content_change(self): + """Hash should change when manifest content changes.""" + manifest1 = {"key": "value1"} + manifest2 = {"key": "value2"} + + assert compute_manifest_hash(manifest1) != compute_manifest_hash(manifest2) + + +class TestGetParentManifestHash: + """Tests for get_parent_manifest_hash function.""" + + def test_returns_empty_string_if_file_missing(self, tmp_path): + """Should return empty string if manifest doesn't exist yet.""" + missing_path = tmp_path / "nonexistent.json" + + result = get_parent_manifest_hash(missing_path) + + assert result == "" + + def test_returns_hash_if_file_exists(self, tmp_path): + """Should return hash of existing manifest.""" + manifest = {"key": "value"} + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + result = get_parent_manifest_hash(manifest_file) + + # Should be a valid SHA256 hash + assert isinstance(result, str) + assert len(result) == 64 + + def test_hash_value_matches_computed(self, tmp_path): + """Returned hash should match independently computed hash.""" + manifest = {"key": "value"} + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + returned_hash = get_parent_manifest_hash(manifest_file) + computed_hash = compute_manifest_hash(manifest_file) + + assert returned_hash == computed_hash + + +class TestVerifyManifestChainLink: + """Tests for verify_manifest_chain_link function.""" + + def test_valid_chain_link(self, tmp_path): + """Should verify a correct chain link.""" + # Create previous manifest + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + # Create current manifest with correct parent hash + prev_hash = compute_manifest_hash(prev_manifest) + current_manifest = {"version": 2, "parent_manifest_hash": prev_hash} + + assert verify_manifest_chain_link(prev_file, current_manifest) + + def test_broken_chain_link_wrong_hash(self, tmp_path): + """Should fail if parent hash doesn't match.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + # Current manifest with wrong parent hash + current_manifest = {"version": 2, "parent_manifest_hash": "0" * 64} + + assert not verify_manifest_chain_link(prev_file, current_manifest) + + def test_broken_chain_link_missing_hash(self, tmp_path): + """Should fail if current manifest lacks parent_manifest_hash.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + # Current manifest without parent_manifest_hash + current_manifest = {"version": 2} + + # Should treat missing field as empty string, which won't match + assert not verify_manifest_chain_link(prev_file, current_manifest) + + def test_chain_link_with_file_path(self, tmp_path): + """Should accept file paths for both previous and current manifests.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + prev_hash = compute_manifest_hash(prev_file) + current_manifest = {"version": 2, "parent_manifest_hash": prev_hash} + current_file = tmp_path / "manifest_v2.json" + current_file.write_text(json.dumps(current_manifest)) + + assert verify_manifest_chain_link(prev_file, current_file) + + def test_chain_link_previous_file_missing(self, tmp_path): + """Should raise FileNotFoundError if previous manifest doesn't exist.""" + current_manifest = {"version": 2, "parent_manifest_hash": "abc"} + + with pytest.raises(FileNotFoundError): + verify_manifest_chain_link(tmp_path / "missing.json", current_manifest) + + def test_chain_link_current_file_missing(self, tmp_path): + """Should raise FileNotFoundError if current manifest file doesn't exist.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + with pytest.raises(FileNotFoundError): + verify_manifest_chain_link(prev_file, tmp_path / "missing.json") + + def test_chain_link_current_malformed(self, tmp_path): + """Should raise ValueError if current manifest is malformed.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + bad_file = tmp_path / "bad.json" + bad_file.write_text("{invalid") + + with pytest.raises(ValueError): + verify_manifest_chain_link(prev_file, bad_file) + + +class TestVerifyManifestChain: + """Tests for verify_manifest_chain function.""" + + def test_chain_report_structure(self, tmp_path): + """Report should have expected keys.""" + manifest = {"key": "value"} + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + report = verify_manifest_chain(manifest_file) + + assert "chain_valid" in report + assert "chain_message" in report + assert "has_parent_hash_field" in report + assert "parent_hash_value" in report + + def test_missing_manifest(self, tmp_path): + """Should report invalid for missing manifest.""" + report = verify_manifest_chain(tmp_path / "nonexistent.json") + + assert report["chain_valid"] is False + assert "not found" in report["chain_message"] + + def test_first_run_manifest_empty_parent(self, tmp_path): + """First-run manifest with empty parent_manifest_hash should be valid.""" + manifest = { + "key": "value", + "parent_manifest_hash": "", + } + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + report = verify_manifest_chain(manifest_file) + + assert report["chain_valid"] is True + assert report["has_parent_hash_field"] is True + assert report["parent_hash_value"] == "" + + def test_old_manifest_no_parent_field(self, tmp_path): + """Old manifest without parent_manifest_hash field.""" + manifest = {"key": "value"} + manifest_file = tmp_path / "manifest.json" + manifest_file.write_text(json.dumps(manifest)) + + report = verify_manifest_chain(manifest_file) + + assert report["has_parent_hash_field"] is False + assert report["parent_hash_value"] == "" + + def test_with_previous_manifest_valid_link(self, tmp_path): + """Should verify link when previous manifest is provided.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + prev_hash = compute_manifest_hash(prev_file) + current_manifest = {"version": 2, "parent_manifest_hash": prev_hash} + current_file = tmp_path / "manifest_v2.json" + current_file.write_text(json.dumps(current_manifest)) + + report = verify_manifest_chain(current_file, previous_manifest_path=prev_file) + + assert report["chain_valid"] is True + + def test_with_previous_manifest_broken_link(self, tmp_path): + """Should report broken link when provided.""" + prev_manifest = {"version": 1} + prev_file = tmp_path / "manifest_v1.json" + prev_file.write_text(json.dumps(prev_manifest)) + + current_manifest = {"version": 2, "parent_manifest_hash": "0" * 64} + current_file = tmp_path / "manifest_v2.json" + current_file.write_text(json.dumps(current_manifest)) + + report = verify_manifest_chain(current_file, previous_manifest_path=prev_file) + + assert report["chain_valid"] is False + assert "broken" in report["chain_message"].lower() + + +class TestIntegrationChainSequence: + """Integration tests for a sequence of manifests.""" + + def test_three_manifest_chain(self, tmp_path): + """Verify a chain of three manifests.""" + # Manifest 1 (first run) + m1 = {"run": 1} + m1_file = tmp_path / "manifest_1.json" + m1_file.write_text(json.dumps(m1)) + + m1_hash = compute_manifest_hash(m1_file) + + # Manifest 2 (references 1) + m2 = {"run": 2, "parent_manifest_hash": m1_hash} + m2_file = tmp_path / "manifest_2.json" + m2_file.write_text(json.dumps(m2)) + + m2_hash = compute_manifest_hash(m2_file) + + # Manifest 3 (references 2) + m3 = {"run": 3, "parent_manifest_hash": m2_hash} + m3_file = tmp_path / "manifest_3.json" + m3_file.write_text(json.dumps(m3)) + + # Verify each link + assert verify_manifest_chain_link(m1_file, m2) + assert verify_manifest_chain_link(m2_file, m3) + + # Verify m3 knows about m2 + report3 = verify_manifest_chain(m3_file) + assert report3["has_parent_hash_field"] is True + assert report3["parent_hash_value"] == m2_hash + + def test_tampered_manifest_breaks_chain(self, tmp_path): + """Modifying an earlier manifest breaks the chain.""" + # Create chain: M1 -> M2 -> M3 + m1 = {"run": 1} + m1_file = tmp_path / "manifest_1.json" + m1_file.write_text(json.dumps(m1)) + + m1_hash = compute_manifest_hash(m1_file) + m2 = {"run": 2, "parent_manifest_hash": m1_hash} + m2_file = tmp_path / "manifest_2.json" + m2_file.write_text(json.dumps(m2)) + + m2_hash = compute_manifest_hash(m2_file) + m3 = {"run": 3, "parent_manifest_hash": m2_hash} + m3_file = tmp_path / "manifest_3.json" + m3_file.write_text(json.dumps(m3)) + + # Verify chain is intact + assert verify_manifest_chain_link(m2_file, m3) + + # Now tamper with M2 + tampered_m2 = {"run": 2, "parent_manifest_hash": m1_hash, "tampered": True} + m2_file.write_text(json.dumps(tampered_m2)) + + # Chain should now be broken + assert not verify_manifest_chain_link(m2_file, m3) + + +class TestBackwardCompatibility: + """Tests for backward compatibility with old manifests.""" + + def test_old_manifest_without_chain_field(self, tmp_path): + """Old manifests without parent_manifest_hash should still work.""" + # Simulate an old manifest + old_manifest = { + "wikipedia_dump": "test.xml.bz2", + "raw_sha256": "abc123", + "processed_sha256": "def456", + # Note: no parent_manifest_hash field + } + old_file = tmp_path / "old_manifest.json" + old_file.write_text(json.dumps(old_manifest)) + + report = verify_manifest_chain(old_file) + + # Should report as non-chain-aware but not fail + assert report["has_parent_hash_field"] is False + assert report["chain_valid"] is False + + def test_adding_chain_to_old_manifest(self, tmp_path): + """Should be able to add chain support by adding parent_manifest_hash.""" + # Old manifest + old_manifest = {"run": 1} + old_file = tmp_path / "manifest_old.json" + old_file.write_text(json.dumps(old_manifest)) + + old_hash = compute_manifest_hash(old_file) + + # New manifest adds parent reference + new_manifest = {"run": 2, "parent_manifest_hash": old_hash} + new_file = tmp_path / "manifest_new.json" + new_file.write_text(json.dumps(new_manifest)) + + # Chain should be verifiable + assert verify_manifest_chain_link(old_file, new_manifest) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_tokenizer.py b/tests/test_tokenizer.py index 1c43f3c..85d13a1 100644 --- a/tests/test_tokenizer.py +++ b/tests/test_tokenizer.py @@ -3,9 +3,12 @@ import pytest from openverifiablellm.tokenizer import ( + create_tokenizer, hash_tokenizer_config, train_tokenizer, ) +from openverifiablellm.tokenizer.bpe_tokenizer import BPETokenizer +from openverifiablellm.tokenizer.sentencepiece_tokenizer import SentencePieceTokenizer @pytest.fixture @@ -166,3 +169,26 @@ def test_hash_tokenizer_missing_merges(tmp_path): with pytest.raises(FileNotFoundError): hash_tokenizer_config(tokenizer_path) + + +# --------------------------------------------------------------------- +# create_tokenizer Tests +# --------------------------------------------------------------------- + + +def test_create_tokenizer_bpe(): + """Test that create_tokenizer returns a BPETokenizer for 'bpe'.""" + tokenizer = create_tokenizer("bpe", vocab_size=1000, min_frequency=2) + assert isinstance(tokenizer, BPETokenizer) + + +def test_create_tokenizer_sentencepiece(): + """Test that create_tokenizer returns a SentencePieceTokenizer for 'sentencepiece'.""" + tokenizer = create_tokenizer("sentencepiece", vocab_size=1000, min_frequency=2) + assert isinstance(tokenizer, SentencePieceTokenizer) + + +def test_create_tokenizer_invalid(): + """Test that create_tokenizer raises a ValueError for invalid types.""" + with pytest.raises(ValueError, match="Unsupported tokenizer: invalid"): + create_tokenizer("invalid", vocab_size=1000, min_frequency=2) diff --git a/tests/test_util.py b/tests/test_util.py index 43e6c1f..1ffa910 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -125,7 +125,6 @@ def test_file_not_found(): def test_extract_text_from_xml_end_to_end(tmp_path, monkeypatch): - xml_content = """ @@ -152,7 +151,6 @@ def test_extract_text_from_xml_end_to_end(tmp_path, monkeypatch): def test_extract_text_from_xml_uncompressed(tmp_path, monkeypatch): - xml_content = """ @@ -318,3 +316,59 @@ def test_export_and_load_merkle_proof(tmp_path): chunk_data=chunk, expected_root=root, ) + + +def test_extract_text_from_xml_malformed_xml(tmp_path, monkeypatch): + import defusedxml.ElementTree as ET + + malformed_xml_content = """ + + + + Hello [[Malformed]] + + + + """ + + input_file = tmp_path / "simplewiki-20260201-pages-malformed.xml" + + with open(input_file, "w", encoding="utf-8") as f: + f.write(malformed_xml_content) + + monkeypatch.chdir(tmp_path) + + with pytest.raises(ET.ParseError): + utils.extract_text_from_xml(input_file) + + +# --------------- load_merkle_proof tests ------------------------------------ + + +def test_load_merkle_proof_valid_file(tmp_path): + proof_data = { + "chunk_index": 1, + "chunk_size": 8, + "proof": [["00" * 32, True]], + } + proof_file = tmp_path / "proof.json" + proof_file.write_text(json.dumps(proof_data)) + + loaded_proof = utils.load_merkle_proof(proof_file) + + assert loaded_proof == proof_data + + +def test_load_merkle_proof_missing_file(tmp_path): + proof_file = tmp_path / "missing.json" + + with pytest.raises(FileNotFoundError): + utils.load_merkle_proof(proof_file) + + +def test_load_merkle_proof_invalid_json(tmp_path): + proof_file = tmp_path / "invalid.json" + proof_file.write_text("{invalid json}") + + with pytest.raises(json.JSONDecodeError): + utils.load_merkle_proof(proof_file) diff --git a/tests/test_verify.py b/tests/test_verify.py index 931a66e..ce6f015 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -367,10 +367,11 @@ def setUp(self): def test_merkle_checks_are_skipped(self): r = verify_preprocessing(self.dump, project_root=self.tmp) - for name in ("raw_merkle_root", "processed_merkle_root"): + for name in ("raw_merkle_root", "processed_merkle_root", "manifest_chunk_size_bytes"): c = next((x for x in r.checks if x.name == name), None) self.assertIsNotNone(c, f"check '{name}' not found") self.assertEqual(c.status, CheckStatus.SKIP) + self.assertIn("Field absent from manifest (older version)", c.detail) def test_other_checks_still_pass(self): r = verify_preprocessing(self.dump, project_root=self.tmp) diff --git a/uv.lock b/uv.lock index 3453d6a..5847b2b 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.9" resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] @@ -143,7 +144,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -167,7 +168,8 @@ name = "filelock" version = "3.25.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ @@ -191,7 +193,8 @@ name = "fsspec" version = "2026.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ @@ -277,13 +280,216 @@ name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, + { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, + { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +] + [[package]] name = "openverifiablellm" version = "0.1.0" @@ -296,6 +502,9 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "ruff" }, @@ -310,6 +519,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "numpy" }, { name = "pytest", specifier = ">=7.0" }, { name = "ruff", specifier = ">=0.15.4" }, ] @@ -367,7 +577,8 @@ name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },